// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#pragma once

#include <stdint.h>
#include <type_traits>

namespace fbl {
namespace tests {

namespace internal {

// A templated implementation of a linear feedback shift register for various
// core state sizes.  With proper selection of the generator, the LFSR will be a
// maximum-cycle LFSR meaning that it will cycle through all of its core states
// (except for all zeros) exactly once before repeating.
template <typename CoreType, CoreType generator>
class Lfsr {
 public:
  static_assert(std::is_unsigned_v<CoreType>, "LFSR core type must be an unsigned integer!");

  constexpr explicit Lfsr(CoreType initial_core) : core_(initial_core) {}

  template <typename T>
  void SetCore(T val) {
    static_assert(std::is_unsigned_v<T>, "LFSR initializer type must be an unsigned integer!");
    core_ = static_cast<CoreType>(val);
  }

  CoreType PeekCore() const { return core_; }

  CoreType GetNext() {
    CoreType ret = 0u;
    CoreType flag = 1u;

    for (size_t i = 0; i < (sizeof(size_t) << 3); ++i) {
      bool bit = core_ & 1u;
      core_ = static_cast<CoreType>(core_ >> 1u);
      if (bit) {
        core_ ^= generator;
        ret |= flag;
      }

      flag = static_cast<CoreType>(flag << 1u);
    }

    return ret;
  }

 private:
  CoreType core_;
};

}  // namespace internal

// User-facing implementation of LFSRs of various sizes with pre-selected
// maximum-cycle generators.
template <typename T, typename Enable = void>
class Lfsr;

// MAKE_LFSR
//
// Temporary macro which deals with the boilerplate declaration of an LFSR of a
// particular core size with a particular generator, exposing the constructor in
// the process.
#define MAKE_LFSR(_bits, _gen)                                                                    \
  template <typename T>                                                                           \
  class Lfsr<T, std::enable_if_t<std::is_unsigned_v<T> && ((sizeof(T) << 3) == _bits)>>           \
      : public internal::Lfsr<uint##_bits##_t, _gen> {                                            \
   public:                                                                                        \
    using CoreType = uint##_bits##_t;                                                             \
                                                                                                  \
    template <typename U>                                                                         \
    constexpr explicit Lfsr(U initial_core)                                                       \
        : internal::Lfsr<CoreType, _gen>(static_cast<CoreType>(initial_core)) {                   \
      static_assert(std::is_unsigned_v<U>, "LFSR initializer type must be an unsigned integer!"); \
    }                                                                                             \
                                                                                                  \
    constexpr Lfsr() : internal::Lfsr<CoreType, _gen>(1u) {}                                      \
  }

MAKE_LFSR(8, 0xB8);
MAKE_LFSR(16, 0xB400);
MAKE_LFSR(32, 0xA3000000);
MAKE_LFSR(64, 0xD800000000000000);

#undef MAKE_LFSR

}  // namespace tests
}  // namespace fbl
